﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WebCrawling.Data.Extracting
{
    public delegate void Task(object Parameters);

    class ThreadPool
    {
        public int MaxThreads { get; set; }
        public int MaxQueueLengh { get; set; }
        public int RetryTimesForFailedTasks { get; set; }
        public Dictionary<Task, List<object>> Tasks { get; set; }
        public Dictionary<Task, List<object>> FailedTasks { get; set; }
        public bool ThrowInnerException { get; set; }

        private object concurrencyLock = new object();
        private object failedTaskLock = new object();
        public ThreadPool(int MaxThreads)
        {
            this.MaxThreads = MaxThreads;
            this.MaxQueueLengh = this.MaxThreads;
            this.Tasks = new Dictionary<Task, List<object>>();
            this.FailedTasks = new Dictionary<Task, List<object>>();
            this.RunningThreads = 0;
            this.CurrentQueueLengh = 0;
            this.RetryTimesForFailedTasks = 0;//default to no retry
            this.ThrowInnerException = false;//default to no raising thread error
            System.Threading.Thread monitor = new System.Threading.Thread(new System.Threading.ThreadStart(this.RunTasks));
            monitor.IsBackground = true;//don't let main thread wait for this thread
            monitor.Start();
        }
        private int RunningThreads;
        private int CurrentQueueLengh;
        public void WaitForTasksToComplete()
        {
            while (this.Tasks.Count > 0 || this.RunningThreads > 0) System.Threading.Thread.Sleep(1000);
            //retry failed tasks
            if (this.RetryTimesForFailedTasks > 0)
            {
                lock (failedTaskLock)
                {
                    foreach (Task task in this.FailedTasks.Keys)
                        foreach (object parameters in this.FailedTasks[task])
                            this.AddTask(task, parameters);

                    this.RetryTimesForFailedTasks--;
                    this.FailedTasks.Clear();
                }
                this.WaitForTasksToComplete();
            }
        }
        public void AddTask(Task task, object Parameters)
        {
            //wait until the queue is not too long
            while (this.CurrentQueueLengh > this.MaxQueueLengh)
                System.Threading.Thread.Sleep(1000);

            lock (concurrencyLock)
            {
                if (this.Tasks.ContainsKey(task))
                {
                    this.Tasks[task].Add(Parameters);
                }
                else
                {
                    this.Tasks[task] = new List<object>();
                    this.Tasks[task].Add(Parameters);
                }
                this.CurrentQueueLengh++;
            }
        }
        private void AddFailedTask(Task task, object Parameters)
        {
            lock (failedTaskLock)
            {
                if (this.FailedTasks.ContainsKey(task))
                {
                    this.FailedTasks[task].Add(Parameters);
                }
                else
                {
                    this.FailedTasks[task] = new List<object>();
                    this.FailedTasks[task].Add(Parameters);
                }
            }
        }
        private void RunTasks()
        {
            while (true)
            {
                if (this.Tasks.Count > 0)
                {
                    lock (concurrencyLock)
                    {
                        if (this.RunningThreads <= this.MaxThreads)
                        {
                            Task currentTask = this.Tasks.Keys.First();

                            if (this.Tasks[currentTask].Count > 0)
                            {
                                System.Threading.Thread th = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(this.CallTask));
                                th.Start(new object[] { currentTask, this.Tasks[currentTask][0] });
                                this.Tasks[currentTask].RemoveAt(0);//remove the task from pool
                            }
                            else//if all tasks in this delegate were done
                            {
                                this.Tasks.Remove(currentTask);
                            }
                        }//if there is worker
                    }//lock
                }//if there is tasks
                System.Threading.Thread.Sleep(2000);
            }//end while
        }
        private void CallTask(object Parameters)
        {
            lock (concurrencyLock)
            {
                this.RunningThreads++;
                this.CurrentQueueLengh--;
            }

            Exception ex = null;
            try
            {
                Task currentTask = (Task)(((object[])Parameters)[0]);
                currentTask(((object[])Parameters)[1]);
            }
            catch (Exception inner_ex)
            {
                lock (concurrencyLock)
                {
                    ex = inner_ex;
                    this.AddFailedTask((Task)(((object[])Parameters)[0]), ((object[])Parameters)[1]);
                }
            }

            lock (concurrencyLock)
            {
                this.RunningThreads--;
            }

            if (ex != null && this.ThrowInnerException) throw ex;

        }
    }
}
